/*
 *    Copyright 2009-2025 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.builder.ResultMappingConstructorResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * 解析映射配置文件 xxxMapper.xml
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;
  private Class<?> mapperClass;

  @Deprecated(since = "3.6.0", forRemoval = true)
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
      String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource,
      Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
        resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
      Map<String, XNode> sqlFragments, Class<?> mapperClass) {
    this(inputStream, configuration, resource, sqlFragments, mapperClass.getName());
    this.mapperClass = mapperClass;
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
      Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
      Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
        resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
      Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  /**
   * 解析映射配置文件
   */
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 完成了第一次 映射配置文件的 解析
      configurationElement(parser.evalNode("/mapper"));
      // 将resource添加到Configuration.loadedResources集合中保存
      configuration.addLoadedResource(resource);
      // 注册mapper接口
      bindMapperForNamespace();
    }
    // 调用这三个方法处理 第一次解析，存入Configuration.incompleteXXX ,,的错误解析集合，，，，重新处理这些解析错误的节点
    // 处理configurationElement()方法中解析失败的 <resultMap>
    configuration.parsePendingResultMaps(false);
    // 重新解析之前解析报错的<cache-ref>
    configuration.parsePendingCacheRefs(false);
    // 重新解析之前解析报错的sql语句节点
    configuration.parsePendingStatements(false);
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  /**
   * 解析映射配置文件 ，，，，  解析这个映射配置文件时，是按照从头到尾的顺序解析的，，但是有时候在解析一个节点的时候，会引用定义在该节点之后的，还未解析的节点
   * 就会导致解析失败，，并抛出IncompleteElementException
   * @param context
   */
  private void configurationElement(XNode context) {
    try {
      // 获取<mapper>节点的namespace属性
      String namespace = context.getStringAttribute("namespace");
      // namespace不能唯恐
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 设置MapperBuilderAssistant的currentNamespace 字段，记录当前的namespace
      builderAssistant.setCurrentNamespace(namespace);
      // 解析<cache-ref>节点
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析<cache>节点
      cacheElement(context.evalNode("cache"));
      // 解析<parameterMap>节点 改节点已废弃
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析<resultMap>节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析<sql>节点
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析<select|insert|update|delete>节点
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
          requiredDatabaseId, mapperClass);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  /**
   * 解析<cache-ref>节点，,,,
   *
   * 解析<cache>节点，会为每一个namespace创建一个对应的Cache对象，，并且在Configuration的caches集合中记录下来，，，
   * namespace和cache对象 一一对应，，，
   * 如果我们希望多个namespace共用同一个 二级缓存，即同一个cache对象，，，，可以使用cache-ref节点进行配置
   *
   *
   * Configuration.cacheRefMap,,这个集合是HashMap，，，
   * 其中key是当前节点所在的namespace，，value是指向被引用的cache对象的namespace
   * 前面一个的namespace和后面一个namespace都指向同一个cache对象
   *
   * @param context
   */
  private void cacheRefElement(XNode context) {
    if (context != null) {
      // 记录到 Configuration.cacheRefMap集合中
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      // 创建CacheRefResolver对象 ？？？？？？？？？？？
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant,
          context.getStringAttribute("namespace"));
      try {
        // 解析Cache引用，主要是设置MapperBuilderAssistant中的currentCache 和 unresolvedCacheRef字段
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 如果解析过程中出现异常，则添加到Configuration.incompleteCacheRefs集合，，稍后在解析
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  /**
   * 解析<cache>节点，，， mybatis的二级缓存，默认没有开启，，如果要为某个命名空间开启二级缓存，，则需要
   * 在相应映射配置文件中添加<cache>节点，，还可以通过<cache>节点的相关属性，为二级缓存配置相应的特性
   * （本质上就是添加相应的装饰器？？？？？/）
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 获取cache节点的 type属性
      String type = context.getStringAttribute("type", "PERPETUAL");
      // 查找type属性对应的 Cache接口实现
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 获取cache节点的eviction属性，，驱逐策略，，，LRU:least recently used
      String eviction = context.getStringAttribute("eviction", "LRU");
      // 解析eviction属性指定的 Cache装饰器类型
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 获取cache节点的flushInterval属性，刷新间隔，，，默认是null
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 获取cache节点的size属性，缓存大小，默认是null
      Integer size = context.getIntAttribute("size");
      // 获取cache节点的readOnly属性，是否只读，默认是false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 获取cache节点的blocking属性，是否阻塞，默认是false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 获取<cache>节点下的子节点，，用来初始化二级缓存
      Properties props = context.getChildrenAsProperties();
      // MapperBuilderAssistant创建Cache对象，并添加到Configuration.cache集合中保存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
            javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  /**
   * 解析所有的 <resultMap>节点
   * @param list
   */
  private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        // 处理单个<resultMap>节点
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  /**
   *  处理单个<resultMap>节点
   * @return
   */
  private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  /**
   *  处理单个<resultMap>节点
   * @param resultMapNode
   * @param additionalResultMappings
   * @param enclosingType
   * @return
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
      Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取<resultMap>节点的type属性，表明结果集将被映射成 type指定类型的对象
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }

    // 获取<resultMap>节点的id属性，唯一标识
    String id = resultMapNode.getStringAttribute("id", resultMapNode::getValueBasedIdentifier);
    // 获取<resultMap>节点的extends属性，该属性指定了该<resultMap>节点的继承关系
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取<resultMap>节点的autoMapping属性，，将该属性设置为true，表示自动映射，，，即自动查找与列名同名的属性名，，并调用setter方法
    // 设置为false后，则需要在<resultMap>节点内明确标注映射关系才会调用对应的setter方法
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

    Discriminator discriminator = null;
    // 该集合用于记录解析的结果
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);

    // 处理<resultMap>的子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 处理<constructor>节点
        processConstructorElement(resultChild, typeClass, resultMappings, id);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 处理<discriminator>节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        // 处理 <id> <result> <association> <collection> 节点
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          // 如果是<id>节点，，则添加到flags集合中
          flags.add(ResultFlag.ID);
        }
        // 创建resultMapping对象，并添加到resultMappings集合中保存
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }

    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
        resultMappings, autoMapping);
    try {
      // 创建ResultMap对象，并添加到Configuration.resultMaps集合中
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      String property = resultMapNode.getStringAttribute("property");
      if (property != null && enclosingType != null) {
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
        return metaResultType.getSetterType(property);
      }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      return enclosingType;
    }
    return null;
  }

  /**
   * 处理 <constructor>节点
   * @param resultChild
   * @param resultType
   * @param resultMappings
   * @param id
   */
  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings,
      String id) {
    // 获取<constructor>节点的子节点
    List<XNode> argChildren = resultChild.getChildren();

    final List<ResultMapping> mappings = new ArrayList<>();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<>();
      // 添加CONSTRUCTOR的标志
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        // 如果是<idArg>节点，则添加ID的标志
        flags.add(ResultFlag.ID);
      }

      // 创建ResultMapping对象，并添加到mappings集合中
      mappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }

    final ResultMappingConstructorResolver resolver = new ResultMappingConstructorResolver(configuration, mappings,
        resultType, id);
    resultMappings.addAll(resolver.resolveWithConstructor());
  }

  /**
   * <discriminator javaType="com.example.UserType" column="user_type"
   *               typeHandler="org.apache.ibatis.type.EnumTypeHandler">
   *     <case value="ADMIN" resultMap="adminUserMap"/>
   *     <case value="NORMAL" resultMap="normalUserMap"/>
   * </discriminator>
   * 处理<discriminator>节点 ： 动态决定用哪个resultMap
   * @param context
   * @param resultType
   * @param resultMappings
   * @return
   */
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
      List<ResultMapping> resultMappings) {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 处理<discriminator>节点的子节点
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      // 调用processNestedResultMappings()方法创建嵌套的ResultMap对象
      String resultMap = caseChild.getStringAttribute("resultMap",
          () -> processNestedResultMappings(caseChild, resultMappings, resultType));
      // 记录该列值与对应选择的ResultMap的id
      discriminatorMap.put(value, resultMap);
    }
    // 创建Discriminator对象
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
        discriminatorMap);
  }


  /**
   * 解析<sql>节点
   * @param list
   */
  private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 获取databaseId属性
      String databaseId = context.getStringAttribute("databaseId");
      // 获取id属性
      String id = context.getStringAttribute("id");
      // 为id添加命名空间
      id = builderAssistant.applyCurrentNamespace(id, false);
      // 检测<sql>的databaseId与当前Configuration中记录的 databaseId是否一致 ？？？？？？？？？？？？？
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        // 记录到XMlMapperBuilder.sqlFragments中保存
        sqlFragments.put(id, context);
      }
    }
  }

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
      return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
  }

  /**
   * 为<result>节点创建对应的ResultMapping对象，，， 比如说解析<constructor></constructor>解析<association></association>
   * @param context
   * @param resultType
   * @param flags
   * @return
   */

  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      // 获取节点的属性 ，，property，column，javaType，jdbcType等
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");

    // 如果未指定 <association>节点的resultMap属性，则是匿名的嵌套映射，，，则需要通过processNestedResultMappings()方法解析该匿名的嵌套映射
    // 在后面分析<collection>节点时，还会涉及匿名嵌套映射的解析过程
    String nestedResultMap = context.getStringAttribute("resultMap",
        () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy"
        .equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 解析javaType，typeHandler， 等对应的class
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 创建ResultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
        nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

  /**
   * 对其中匿名的嵌套映射处理
   * 解析 <association>没有设置resultMap的嵌套映射
   * @param context
   * @param resultMappings
   * @param enclosingType
   * @return
   */
  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings,
      Class<?> enclosingType) {
    // 只会处理这三种节点
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
        && context.getStringAttribute("select") == null) {
      validateCollection(context, enclosingType);
      // 创建ResultMap，，并添加到Configuration.resultMaps集合中
      ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
      return resultMap.getId();
    }
    return null;
  }

  protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
        && context.getStringAttribute("javaType") == null) {
      MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
      String property = context.getStringAttribute("property");
      if (!metaResultType.hasSetter(property)) {
        throw new BuilderException(
            "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
      }
    }
  }

  /**
   * 映射配置文件与对应Mapper接口的绑定
   */
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 解析名称空间对应的类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      // 是否已经加载了这个Mapper接口
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        // 追加名称空间到Configuration.loadedResources中
        configuration.addLoadedResource("namespace:" + namespace);
        // 调用MapperRegistry.addMapper()方法，注册 mapper接口，，
        // 这个方法会向 MapperRegistry.knownMappers集合注册指定的Mapper几口，
        // ，其实该方法还会创建MapperAnnotationBuilder,,,并调用MapperAnnotationBuilder.parse()方法解析Mapper接口中的注解信息
        configuration.addMapper(boundType);
      }
    }
  }

}
